namespace Stee.CAP8.Healthy
{
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using NLog;
    using Polly;
    using Stee.CAP8.Health;

    public class HealthService
    {
        private const int RETRY_INTERVAL = 15;
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
        private TcpListener server = null;
        private Socket socket = null;
        private bool isServiceStopping = false;
        private bool isServiceStarted = false;
        #region Singleton
        private static volatile HealthService instance;
        private readonly static object SynRoot = new object();

        public static HealthService Instance
        {
            get
            {
                if (instance == null)
                {
                    lock (SynRoot)
                    {
                        if (instance == null)
                        {
                            instance = new HealthService();
                        }
                    }
                }

                return instance;
            }
        }

        private HealthService()
        {

        }
        #endregion
        /// <summary>
        /// Start Health service by configurated address and port.
        /// </summary>
        public void Start()
        {
            //get configuration


        }
        public void Start(string address, int port)
        {
            Logger.Trace($"Start Health service. address:{address}, port:{ port}");
            try
            {
                this.isServiceStopping = false;
                var localAddr = IPAddress.Parse(address);
                server = new TcpListener(localAddr, port);

                Policy.Handle<Exception>((ex) =>
                {
                    if (isServiceStopping)
                    {
                        return false;
                    }
                    else
                    {
                        Logger.Error(ex, "Retry connection...");
                    }
                    return true;
                }).WaitAndRetryForever(retryattempt => TimeSpan.FromSeconds(RETRY_INTERVAL))
                    .Execute(() =>
                    {
                        // Start listening for client requests.
                        Logger.Info("Start listener...");
                        server.Start();
                        StartListener();
                        Logger.Info("Listener started.");
                    });
                isServiceStarted = true;
            }
            catch (SocketException e)
            {
                Logger.Fatal(e, "The socket for health service cannot be opened.");
                throw;
            }
            catch (Exception e)
            {
                Logger.Fatal(e, "The Health service cannot be started.");
                throw;
            }
            finally
            {
                // Stop listening for new clients.
                if (server != null && server.Server.IsBound)
                {
                    server.Stop();
                }
            }

        }

        public void StartListener()
        {
            try
            {
                while (!isServiceStopping)
                {
                    if (Logger.IsTraceEnabled)
                    {
                        Logger.Info("Wait for connection...");
                    }
                    socket = server.AcceptSocket();
                    Logger.Info($"Connected to {socket.RemoteEndPoint} !");
                    var t = new Thread(new ParameterizedThreadStart(HandleHealthRequest));
                    t.Start(socket);
                }
            }
            catch (SocketException e)
            {
                Logger.Error(e, "Connection exception.");
                throw;
            }
        }

        private void HandleHealthRequest(object obj)
        {
            if (obj is Socket clientSocket)
            {
                var remoteAddress = clientSocket.RemoteEndPoint as IPEndPoint;
                
                try
                {
                    var status = GetServiceStatus();
                    var byteStatus = BitConverter.GetBytes(status);
                    clientSocket.Send(byteStatus);
                    clientSocket.Close();
                    clientSocket.Dispose();
                    if (Logger.IsInfoEnabled)
                    {
                        Logger.Info($"Returned Health status {status} to address: {remoteAddress?.Address} ");
                    }
                }
                catch (SocketException e)
                {
                    Logger.Error(e, $"Handle Request from address: {remoteAddress?.Address} failed.");
                }
            }
            else
            {
                Logger.Error("Object is not socket.");
            }

        }

        private int GetServiceStatus()
        {
            ServiceStatus status;
            if (ServiceContext.IsReady())
            {
                if (ServiceContext.IsDegraded())
                {
                    status = ServiceStatus.Degraded;
                }
                else
                {
                    status = ServiceStatus.Serving;
                }
            }
            else
            {
                status = ServiceStatus.NotServing;
            }
            return (int)status;

        }

        public void Stop()
        {
            isServiceStopping = true;
            if (isServiceStarted)
            {
                socket.Close();
                server.Stop();
            }
            isServiceStarted = false;

        }

    }
}
